from imutils import paths
import numpy as np
import argparse
import imutils
import cv2

#执行
#python stitcher_cmd.py --images images/ --output output.png

# 构造参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--images",default='images', type=str, required=True,
                help="path to input directory of images to stitch")
ap.add_argument("-o", "--output", default='output.png',type=str, required=True,
                help="path to the output image")
ap.add_argument("-c", "--crop", type=int, default=0,
                help="whether to crop out largest rectangular region")
args = vars(ap.parse_args())  # vars函数是实现返回对象object的属性和属性值的字典对象

print(args)  # {'images': 'images/scottsdale', 'output': 'output.png', 'crop': 1}
# 匹配输入图像的路径并初始化我们的图像列表
# rectangular_region = 2
print("[INFO] loading images...")
# 获取到每张待拼接图像并排序，如['第一张图片路径'， 第二张图片路径'，第三张图片路径']
imagePaths = sorted(list(paths.list_images(args["images"])))
# print(imagePaths)
# imagePaths = ['IMG_1786-2.jpg',
# 			  'IMG_1787-2.jpg',
# 			  'IMG_1788-2.jpg']
images = []

# 遍历图像路径，加载每个路径，然后将它们添加到我们的路径中图像到stich列表
for imagePath in imagePaths:
    image = cv2.imread(imagePath)
    images.append(image)

# 初始化OpenCV的图像sticher对象，然后执行图像拼接
print("[INFO] stitching images...")
stitcher = cv2.createStitcher() if imutils.is_cv3() else cv2.Stitcher_create()
(status, stitched) = stitcher.stitch(images)


# print(status, stitched)
# 如果状态为“0”，则OpenCV成功执行图像拼接
if status == 0:
    # 检查我们是否应该从拼接图像中裁剪出最大的矩形区域
    if args["crop"] > 0:
        # 在拼接图像周围创建一个10像素的黑色边框
        print("[INFO] cropping...")
        stitched = cv2.copyMakeBorder(stitched, 10, 10, 10, 10,
                                      cv2.BORDER_CONSTANT, (0, 0, 0))
        # cv2.imshow('123',stitched)

        # 将拼接图像转换为灰度
        gray = cv2.cvtColor(stitched, cv2.COLOR_BGR2GRAY)
        # cv2.imshow('456', gray)
        # 对灰度图像进行阈值二值化，
        # 这样所有大于零的像素都设置为255（前景），而其他所有像素都保持为0（背景）
        thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)[1]
        # cv2.imshow('789', thresh)
        # 在阈值图像中找到所有外部轮廓，然后找到 “最大 ”轮廓，它将是拼接图像的轮廓
        # cv2.RETR_EXTERNAL：只找外轮廓。cv2.CHAIN_APPROX_SIMPLE：输出少量轮廓点
        # 输出参数1：图像
        # 输出参数2：轮廓列表
        # 输出参数3：层级
        cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        # print(cnts) #opencv3 中cnts包括三个数组：
        # print(len(cnts))
        #为了不区分opencv2或opencv3
        cnts = imutils.grab_contours(cnts)

        ################################
        # imutils.grab_contours 的源码 ：

            # # if the length the contours tuple returned by cv2.findContours
            # # is '2' then we are using either OpenCV v2.4, v4-beta, or
            # # v4-official
            # if len(cnts) == 2:
            #     cnts = cnts[0]
            # # if the length of the contours tuple is '3' then we are using
            # # either OpenCV v3, v4-pre, or v4-alpha
            # elif len(cnts) == 3:
            #     cnts = cnts[1]
            # # otherwise OpenCV has changed their cv2.findContours return
            # # signature yet again and I have no idea WTH is going on
            # else:
            #     raise Exception(("Contours tuple must have length 2 or 3, "
            #                      "otherwise OpenCV changed their cv2.findContours return "
            #                      "signature yet again. Refer to OpenCV's documentation "
            #                      "in that case"))
            # # return the actual contours array
            # return cnts
        ##################################

        # 抓取具有最大区域的轮廓（即拼接图像本身的轮廓），cv2.contourArea是求轮廓面积
        c = max(cnts, key=cv2.contourArea)
        # print(c)
        # print(c.shape)
        # 为掩码分配内存，该掩码将包含拼接图像区域的矩形边界框
        mask = np.zeros(thresh.shape, dtype="uint8")
        # 计算出最大轮廓的边界框,使用边界矩形信息.使用 cv2.boundingRect(img) 函数，用一个最小的矩形，
        # 把找到的形状包起来，img是一个二值图，也就是它的参数；返回四个值，分别是 x，y，w，h；
        # 其中 x，y 是矩阵左上点的坐标，w，h 是矩阵的宽和高
        (x, y, w, h) = cv2.boundingRect(c)
        # 使用cv2.rectangle给图像加框，我们在mask上绘制一个纯白色矩形。
        # 参数1：图像
        # 参数2：左上角坐标
        # 参数3：右下角坐标
        # 参数4：框的颜色
        # 参数5：框的粗细
        cv2.rectangle(mask, (x, y), (x + w, y + h), 255, -1)

        # 创建掩码的两个副本
        # 第一个mask，将逐渐缩小，直到它可以放入全景内部。
        minRect = mask.copy()
        # 第二个mask，将用于确定是否需要继续减小minRect的大小。
        sub = mask.copy()
        #cv2.imshow('111',sub)

        # 保持循环，直到sub中没有更多的前景像素
        # 对二值化图像执行countNonZero。可得到非零像素点数(即外边框的像素点)
        print(cv2.countNonZero(sub))
        while cv2.countNonZero(sub) > 0:
            # 执行侵蚀形态学操作以减小minRect的大小。
            minRect = cv2.erode(minRect, None)
            #cv2.imshow('333',minRect)
            # 从minRect中减去thresh ，一旦minRect中没有更多的前景像素，我们就可以从循环中断开
            sub = cv2.subtract(minRect, thresh)

        # 在最小矩形掩码中找到轮廓，然后提取边界框（x，y） - 坐标
        cnts = cv2.findContours(minRect.copy(), cv2.RETR_EXTERNAL,
                                cv2.CHAIN_APPROX_SIMPLE)
        #cv2.imshow('444',cnts[0])
        cnts = imutils.grab_contours(cnts)
        c = max(cnts, key=cv2.contourArea)
        (x, y, w, h) = cv2.boundingRect(c)

        # 使用边界框坐标来提取我们的最终拼接图像
        stitched = stitched[y:y + h, x:x + w]

	# 将输出拼接图像写入磁盘
    cv2.imwrite("result.jpg", stitched)

	# 将输出拼接图像显示到我们的屏幕
    cv2.imshow("Stitched", stitched)
    cv2.waitKey(0)

# 否则，拼接失败，可能是由于检测不到足够的关键点

else:
   print("[INFO] image stitching failed ({})".format(status))

